/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.modules.javascript.v8debug.ui.attach;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.netbeans.api.debugger.Properties;
import org.netbeans.modules.javascript.v8debug.V8DebuggerSessionProvider;
import org.netbeans.modules.javascript.v8debug.api.Connector;
import org.netbeans.spi.debugger.ui.Controller;
import org.netbeans.spi.debugger.ui.PersistentController;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.filesystems.FileChooserBuilder;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;

/**
 *
 * @author Martin Entlicher
 */
public class AttachCustomizer extends javax.swing.JPanel {
    
    private final ConnectController controller;
    private final ConnectProperties cproperties = new ConnectProperties();
    private final ValidityDocumentListener validityDocumentListener = new ValidityDocumentListener();

    /**
     * Creates new form AttachCustomizer
     */
    public AttachCustomizer() {
        controller = new ConnectController();
        initComponents();
        portTextField.getDocument().addDocumentListener(validityDocumentListener);
        localSourcesTextField.getDocument().addDocumentListener(validityDocumentListener);
        serverPathTextField.getDocument().addDocumentListener(validityDocumentListener);
        localSourcesCheckBoxActionPerformed(null);
        SwingUtilities.invokeLater(new Runnable(){
            @Override
            public void run() {
                checkValid();
            }
        });
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        hostLabel = new javax.swing.JLabel();
        hostTextField = new javax.swing.JTextField();
        portLabel = new javax.swing.JLabel();
        portTextField = new javax.swing.JTextField();
        localSourcesLabel = new javax.swing.JLabel();
        localSourcesCheckBox = new javax.swing.JCheckBox();
        localSourcesTextField = new javax.swing.JTextField();
        serverPathLabel = new javax.swing.JLabel();
        serverPathTextField = new javax.swing.JTextField();
        localSourcesButton = new javax.swing.JButton();

        hostLabel.setLabelFor(hostTextField);
        org.openide.awt.Mnemonics.setLocalizedText(hostLabel, org.openide.util.NbBundle.getMessage(AttachCustomizer.class, "AttachCustomizer.hostLabel.text")); // NOI18N

        portLabel.setLabelFor(portTextField);
        org.openide.awt.Mnemonics.setLocalizedText(portLabel, org.openide.util.NbBundle.getMessage(AttachCustomizer.class, "AttachCustomizer.portLabel.text")); // NOI18N

        localSourcesLabel.setLabelFor(localSourcesTextField);
        org.openide.awt.Mnemonics.setLocalizedText(localSourcesLabel, org.openide.util.NbBundle.getMessage(AttachCustomizer.class, "AttachCustomizer.localSourcesLabel.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(localSourcesCheckBox, org.openide.util.NbBundle.getMessage(AttachCustomizer.class, "AttachCustomizer.localSourcesCheckBox.text")); // NOI18N
        localSourcesCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                localSourcesCheckBoxActionPerformed(evt);
            }
        });

        serverPathLabel.setLabelFor(serverPathTextField);
        org.openide.awt.Mnemonics.setLocalizedText(serverPathLabel, org.openide.util.NbBundle.getMessage(AttachCustomizer.class, "AttachCustomizer.serverPathLabel.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(localSourcesButton, org.openide.util.NbBundle.getMessage(AttachCustomizer.class, "AttachCustomizer.localSourcesButton.text")); // NOI18N
        localSourcesButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                localSourcesButtonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(21, 21, 21)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createSequentialGroup()
                                .addComponent(serverPathLabel)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(serverPathTextField))
                            .addGroup(layout.createSequentialGroup()
                                .addComponent(localSourcesLabel)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(localSourcesTextField)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                .addComponent(localSourcesButton))))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(localSourcesCheckBox)
                        .addGap(0, 214, Short.MAX_VALUE))
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(hostLabel)
                            .addComponent(portLabel))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(portTextField)
                            .addComponent(hostTextField))))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(hostLabel)
                    .addComponent(hostTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(portLabel)
                    .addComponent(portTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(localSourcesCheckBox)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(localSourcesLabel)
                    .addComponent(localSourcesTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(localSourcesButton))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(serverPathLabel)
                    .addComponent(serverPathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
    }// </editor-fold>//GEN-END:initComponents

    private void localSourcesCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_localSourcesCheckBoxActionPerformed
        boolean areSources = localSourcesCheckBox.isSelected();
        localSourcesLabel.setEnabled(areSources);
        localSourcesTextField.setEnabled(areSources);
        localSourcesButton.setEnabled(areSources);
        serverPathLabel.setEnabled(areSources);
        serverPathTextField.setEnabled(areSources);
        checkValid();
    }//GEN-LAST:event_localSourcesCheckBoxActionPerformed

    private void localSourcesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_localSourcesButtonActionPerformed
        FileChooserBuilder fchb = new FileChooserBuilder(AttachCustomizer.class);
        fchb.setDirectoriesOnly(true);
        File dir = fchb.showOpenDialog();
        if (dir != null) {
            localSourcesTextField.setText(dir.getAbsolutePath());
        }
    }//GEN-LAST:event_localSourcesButtonActionPerformed


    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JLabel hostLabel;
    private javax.swing.JTextField hostTextField;
    private javax.swing.JButton localSourcesButton;
    private javax.swing.JCheckBox localSourcesCheckBox;
    private javax.swing.JLabel localSourcesLabel;
    private javax.swing.JTextField localSourcesTextField;
    private javax.swing.JLabel portLabel;
    private javax.swing.JTextField portTextField;
    private javax.swing.JLabel serverPathLabel;
    private javax.swing.JTextField serverPathTextField;
    // End of variables declaration//GEN-END:variables

    @NbBundle.Messages({"MSG_NoPort=Port number is missing.",
                        "# {0} - invalid port number",
                        "MSG_InvalidPort=Port number {0} is invalid.",
                        "MSG_LocalPathNotExists=The local sources directory does not exist.",
                        "MSG_LocalPathNotDir=The local sources path is not a directory.",
                        "MSG_ServerPathIsEmpty=The server path is empty."})
    private void checkValid() {
        assert SwingUtilities.isEventDispatchThread() : "Called outside of AWT.";
        if (portTextField.getText().isEmpty()) {
            controller.setInformationMessage(Bundle.MSG_NoPort());
            controller.setValid(false);
            return ;
        }
        int port;
        try {
            port = Integer.parseInt(portTextField.getText());
        } catch (NumberFormatException nfex) {
            controller.setErrorMessage(Bundle.MSG_InvalidPort(portTextField.getText()));
            controller.setValid(false);
            return ;
        }
        String localDir = localSourcesTextField.getText();
        if (localSourcesCheckBox.isSelected()) {
            File localFile = new File(localDir);
            if (!localFile.exists()) {
                controller.setInformationMessage(Bundle.MSG_LocalPathNotExists());
                controller.setValid(false);
                return ;
            }
            if (!localFile.isDirectory()) {
                controller.setErrorMessage(Bundle.MSG_LocalPathNotDir());
                controller.setValid(false);
                return ;
            }
            if (serverPathTextField.getText().isEmpty()) {
                controller.setInformationMessage(Bundle.MSG_ServerPathIsEmpty());
                controller.setValid(false);
                return ;
            }
        }
        cproperties.host = hostTextField.getText();
        cproperties.port = port;
        cproperties.hasSources = localSourcesCheckBox.isSelected();
        cproperties.localPath = localDir;
        cproperties.serverPath = serverPathTextField.getText();
        controller.setInformationMessage(null);
        controller.setErrorMessage(null);
        controller.setValid(true);
    }
    
    Controller getController() {
        return controller;
    }
    
    private class ValidityDocumentListener implements DocumentListener {
        @Override
        public void insertUpdate(DocumentEvent e) {
            checkValid();
        }
        @Override
        public void removeUpdate(DocumentEvent e) {
            checkValid();
        }
        @Override
        public void changedUpdate(DocumentEvent e) {
            checkValid();
        }
    }
    
    public class ConnectController implements PersistentController {
        
        private static final String V8_ATTACH_PROPERTIES = "v8_attach_settings";
        private static final String PROP_HOST = "host";
        private static final String PROP_PORT = "port";
        private static final String PROP_HAS_SOURCES = "hasSources";
        private static final String PROP_LOCAL_PATH = "localPath";
        private static final String PROP_SERVER_PATH = "serverPath";
        
        private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
        private boolean valid = true;

        @Override
        public String getDisplayName() {
            return V8DebuggerSessionProvider.getSessionName(cproperties.host, cproperties.port);
        }

        @Override
        public boolean load(Properties props) {
            assert !SwingUtilities.isEventDispatchThread();
            final Properties attachProps = props.getProperties(V8_ATTACH_PROPERTIES);
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    @Override
                    public void run() {
                        hostTextField.setText(attachProps.getString(PROP_HOST, ""));
                        portTextField.setText(Integer.toString(attachProps.getInt(PROP_PORT, 0)));
                        localSourcesCheckBox.setSelected(attachProps.getBoolean(PROP_HAS_SOURCES, false));
                        String localPath = attachProps.getString(PROP_LOCAL_PATH, "");
                        localSourcesTextField.setText(localPath);
                        String serverPath = attachProps.getString(PROP_SERVER_PATH, "");
                        serverPathTextField.setText(serverPath);
                    }
                });
            } catch (InterruptedException ex) {
                Exceptions.printStackTrace(ex);
            } catch (InvocationTargetException ex) {
                Exceptions.printStackTrace(ex);
            }
            return true;
        }

        @Override
        public void save(Properties props) {
            final Properties attachProps = props.getProperties(V8_ATTACH_PROPERTIES);
            if (SwingUtilities.isEventDispatchThread()) {
                saveToProps(attachProps);
            } else {
                try {
                    SwingUtilities.invokeAndWait(new Runnable() {
                        @Override
                        public void run() {
                            saveToProps(attachProps);

                        }
                    });
                } catch (InterruptedException ex) {
                    Exceptions.printStackTrace(ex);
                } catch (InvocationTargetException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
        }
        
        private void saveToProps(Properties attachProps) {
            attachProps.setString(PROP_HOST, hostTextField.getText());
            try {
                attachProps.setInt(PROP_PORT, Integer.parseInt(portTextField.getText()));
            } catch (NumberFormatException nfex) {}
            attachProps.setBoolean(PROP_HAS_SOURCES, localSourcesCheckBox.isSelected());
            attachProps.setString(PROP_LOCAL_PATH, localSourcesTextField.getText());
            attachProps.setString(PROP_SERVER_PATH, serverPathTextField.getText());
        }

        @Override
        public boolean ok() {
            Connector.Properties cp;
            if (!cproperties.hasSources) {
                cp = new Connector.Properties(cproperties.host, cproperties.port);
            } else {
                cp = new Connector.Properties(cproperties.host, cproperties.port,
                                              Collections.singletonList(cproperties.localPath),
                                              Collections.singletonList(cproperties.serverPath),
                                              Collections.EMPTY_SET);
            }
            try {
                Connector.connect(cp, null);
            } catch (IOException ioex) {
                DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(ioex.getLocalizedMessage(), NotifyDescriptor.ERROR_MESSAGE));
                return false;
            }
            return true;
        }

        @Override
        public boolean cancel() {
            return true;
        }

        @Override
        public boolean isValid() {
            return valid;
        }

        void setValid(boolean valid) {
            this.valid = valid;
            firePropertyChange(PROP_VALID, !valid, valid);
        }

        void setErrorMessage(String msg) {
            firePropertyChange(NotifyDescriptor.PROP_ERROR_NOTIFICATION, null, msg);
        }

        void setInformationMessage(String msg) {
            firePropertyChange(NotifyDescriptor.PROP_INFO_NOTIFICATION, null, msg);
        }

        private void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
            pcs.firePropertyChange(propertyName, oldValue, newValue);
        }

        @Override
        public void addPropertyChangeListener(PropertyChangeListener l) {
            pcs.addPropertyChangeListener(l);
        }

        @Override
        public void removePropertyChangeListener(PropertyChangeListener l) {
            pcs.removePropertyChangeListener(l);
        }
        
    }
    
    private static final class ConnectProperties {
        String host;
        int port;
        boolean hasSources;
        String localPath;
        String serverPath;
    }
}
